package com.empire.module.member.service.mappointInarea;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.empire.module.member.config.MemberAppConfig;
import com.empire.module.member.controller.admin.mappolygonpoint.vo.PointInPolygonReqVO;
import com.empire.module.member.controller.admin.mappolygonpoint.vo.PointInPolygonRespVO;
import com.empire.module.member.controller.admin.warningindividual.vo.WarningIndividualSaveReqVO;
import com.empire.module.member.dal.dataobject.mapconfig.MapConfigDO;
import com.empire.module.member.dal.dataobject.mappolygon.MapPolygonDO;
import com.empire.module.member.dal.dataobject.mappolygonpoint.MapPolygonPointDO;
import com.empire.module.member.dal.dataobject.warnconfigpersonrelation.WarnConfigPersonRelationDO;
import com.empire.module.member.dal.dataobject.warningindividual.WarningIndividualDO;
import com.empire.module.member.dal.mysql.mapconfig.MapConfigMapper;
import com.empire.module.member.dal.mysql.mappolygon.MapPolygonMapper;
import com.empire.module.member.dal.mysql.mappolygonpoint.MapPolygonPointMapper;
import com.empire.module.member.dal.mysql.warnconfigpersonrelation.WarnConfigPersonRelationMapper;
import com.empire.module.member.dal.mysql.warningindividual.WarningIndividualMapper;
import com.empire.module.member.service.warningindividual.WarningIndividualService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class PointInPolygonService {

    @Resource
    private MapPolygonMapper mapPolygonMapper;
    @Resource
    private WarnConfigPersonRelationMapper warnConfigPersonRelationMapper;
    @Resource
    private MapPolygonPointMapper mapPolygonPointMapper;
    @Resource
    private MapConfigMapper mapConfigMapper;
    @Resource
    private WarningIndividualService warningIndividualService;
    // 注入配置类
    @Resource
    private MemberAppConfig memberAppConfig;
    @Resource
    private WarningIndividualMapper warningIndividualMapper;
    /*
     * 判断经纬度是否在人员关联的预警多边形内（补充 fenceType=2 逻辑）
     *
     * @param reqVO 入参（人员ID、经度、纬度）
     * @return 包含是否在多边形内、关联多边形列表的响应
     */
    public PointInPolygonRespVO judgePointInPolygon(PointInPolygonReqVO reqVO) {
        // 1. 初始化响应结果（新增：记录点是否实际在圈内）
        PointInPolygonRespVO result = new PointInPolygonRespVO();
        result.setIsInside(false); // 初始化为“不在圈内”
        result.setInsidePolygons(new ArrayList<>());
        LocalDateTime currentTime = LocalDateTime.now();

        // 新增：记录点是否实际在任何一个多边形内（与预警条件无关）
        boolean isActuallyInside = false;

        // 2. 入参校验（保持不变）
        if (!validateReqParam(reqVO)) {
            log.warn("点在多边形判断：入参不合法，reqVO={}", reqVO);
            return result;
        }

        try {
            // 3. 查询人员关联的预警配置ID（保持不变）
            Set<String> configIdSet = getPersonRelatedConfigIds(reqVO.getPersonId());
            if (configIdSet.isEmpty()) {
                log.debug("点在多边形判断：人员[{}]无关联预警配置", reqVO.getPersonId());
                return result;
            }

            // 4. 查询【有效】的预警配置（保持不变）
            List<MapConfigDO> validConfigList = getValidFenceWarnConfig(configIdSet, currentTime);
            if (validConfigList.isEmpty()) {
                log.debug("点在多边形判断：人员[{}]无有效围栏预警配置", reqVO.getPersonId());
                return result;
            }

            Map<String, String> configId2NameMap = validConfigList.stream()
                    .collect(Collectors.toMap(MapConfigDO::getId, MapConfigDO::getName, (k1, k2) -> k2));

            // 5. 遍历每个有效配置，判断点是否在圈内（核心修改）
            for (MapConfigDO configDO : validConfigList) {
                String configId = configDO.getId();
                Integer fenceType = Optional.ofNullable(configDO.getFenceType()).orElse(1); // 默认为禁入

                // 查询配置关联的多边形（保持不变）
                List<MapPolygonDO> polygonList = getConfigRelatedPolygons(configId);
                if (polygonList.isEmpty()) {
                    log.warn("点在多边形判断：预警配置[{}]无关联多边形，跳过", configId);
                    continue;
                }

                // 查询多边形顶点（保持不变）
                Map<String, List<MapPolygonPointDO>> polygon2PointsMap = getPolygonRelatedPoints(polygonList);

                // 核心：判断点是否在当前配置的多边形内（与预警类型无关，仅记录实际位置）
                Point targetPoint = new Point(reqVO.getLng(), reqVO.getLat());
                for (MapPolygonDO polygonDO : polygonList) {
                    List<MapPolygonPointDO> pointDOList = polygon2PointsMap.getOrDefault(polygonDO.getId(), new ArrayList<>());
                    if (pointDOList.size() < 3) continue;

                    // 构建多边形结构并判断点是否在当前多边形内
                    List<List<Point>> polygonStructure = buildPolygonStructure(pointDOList);
                    boolean currentInPolygon = isPointInPolygon(targetPoint, polygonStructure);

                    // 只要有一个多边形包含该点，就标记为“实际在圈内”
                    if (currentInPolygon) {
                        isActuallyInside = true;
                        break; // 已确定在圈内，无需继续判断其他多边形
                    }
                }

                // 按围栏类型筛选预警多边形（保持预警逻辑不变）
                List<PointInPolygonRespVO.PolygonSimpleVO> currentConfigWarnList =
                        judgePointByFenceType(reqVO, polygonList, polygon2PointsMap, configId2NameMap, fenceType);
                result.getInsidePolygons().addAll(currentConfigWarnList);

                // 禁入围栏（fenceType=1）：点在圈内 → 预警（列表非空即满足）
                if (fenceType == 1 && !currentConfigWarnList.isEmpty()) {
                    createFenceWarning(reqVO, configDO, currentConfigWarnList, currentTime, fenceType);
                }

                // 禁出围栏（fenceType=2）：点在圈外 → 预警（列表非空 且 点不在任何圈内）
                if (fenceType == 2 && !isActuallyInside && !currentConfigWarnList.isEmpty()) {
                    createFenceWarning(reqVO, configDO, currentConfigWarnList, currentTime, fenceType);
                }
            }

            // 6. 修正：用实际是否在圈内的状态设置isInside
            result.setIsInside(isActuallyInside);
            log.info("点在多边形判断：最终结果 - 是否实际在圈内={}，符合预警条件的多边形数={}",
                    result.getIsInside(), result.getInsidePolygons().size());

        } catch (Exception e) {
            log.error("点在多边形判断异常：reqVO={}", reqVO, e);
        }

        return result;
    }

    /**
     * 查询人员关联的预警配置ID（去重）
     */
    private Set<String> getPersonRelatedConfigIds(Long personId) {
        return warnConfigPersonRelationMapper.selectList(
                        new LambdaQueryWrapper<WarnConfigPersonRelationDO>()
                                .eq(WarnConfigPersonRelationDO::getPersonId, personId)
                ).stream()
                .map(WarnConfigPersonRelationDO::getWarnConfigId)
                .collect(Collectors.toSet());
    }

    /**
     * 查询配置关联的所有多边形（过滤已删除）
     */
    private List<MapPolygonDO> getConfigRelatedPolygons(String configId) {
        return mapPolygonMapper.selectList(
                new LambdaQueryWrapper<MapPolygonDO>()
                        .eq(MapPolygonDO::getConfigId, configId)
                        .eq(MapPolygonDO::getDeleted, false)
                        .orderByAsc(MapPolygonDO::getSortOrder) // 按排序字段查询，保证一致性
        );
    }

    /**
     * 查询多个多边形的顶点，并按“多边形ID”分组
     */
    private Map<String, List<MapPolygonPointDO>> getPolygonRelatedPoints(List<MapPolygonDO> polygonList) {
        Set<String> polygonIdSet = polygonList.stream()
                .map(MapPolygonDO::getId)
                .collect(Collectors.toSet());

        return mapPolygonPointMapper.selectList(
                        new LambdaQueryWrapper<MapPolygonPointDO>()
                                .in(MapPolygonPointDO::getPolygonId, polygonIdSet)
                                .eq(MapPolygonPointDO::getDeleted, false)
                                // 按“多边形ID→环索引→顶点顺序”排序，确保构建多边形时顶点顺序正确
                                .orderByAsc(MapPolygonPointDO::getPolygonId,
                                        MapPolygonPointDO::getRingIndex,
                                        MapPolygonPointDO::getPointIndex)
                ).stream()
                .collect(Collectors.groupingBy(MapPolygonPointDO::getPolygonId));
    }

    /**
     * 入参校验（人员ID非空、经纬度合法）
     */
    private boolean validateReqParam(PointInPolygonReqVO reqVO) {
        if (reqVO == null || reqVO.getPersonId() == null) {
            return false;
        }
        double lng = reqVO.getLng();
        double lat = reqVO.getLat();
        // 经度范围：-180~180，纬度范围：-90~90
        return lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90;
    }

    // ------------------------------ 新增/修改的工具方法 ------------------------------

    /**
     * 新增：查询有效且类型为“围栏预警”（warnType=1）的配置
     * 过滤条件：1.在时间范围内 2.未删除 3.预警类型为围栏预警
     */
    private List<MapConfigDO> getValidFenceWarnConfig(Set<String> configIdSet, LocalDateTime currentTime) {
        // 转换 LocalDateTime 为 Date（适配 MapConfigDO 中的 startTime/endTime 类型）
        Date currentDate = Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant());

        return mapConfigMapper.selectList(
                new LambdaQueryWrapper<MapConfigDO>()
                        .in(MapConfigDO::getId, configIdSet)
                        .eq(MapConfigDO::getDeleted, false)
                        .eq(MapConfigDO::getWarnType, 1) // 仅筛选“围栏预警”（warnType=1）
                        .le(MapConfigDO::getStartTime, currentDate) // 配置开始时间<=当前时间
                        .ge(MapConfigDO::getEndTime, currentDate) // 配置结束时间>=当前时间
        );
    }

    /**
     * 核心修改：按 fenceType 区分，判断点是否符合预警条件
     *
     * @param fenceType 1=禁入（圈内预警），2=禁出（圈外预警）
     * @return 符合预警条件的多边形列表
     */
    private List<PointInPolygonRespVO.PolygonSimpleVO> judgePointByFenceType(
            PointInPolygonReqVO reqVO, List<MapPolygonDO> polygonList,
            Map<String, List<MapPolygonPointDO>> polygon2PointsMap, Map<String, String> configId2NameMap,
            Integer fenceType) {

        List<PointInPolygonRespVO.PolygonSimpleVO> warnList = new ArrayList<>();
        Point targetPoint = new Point(reqVO.getLng(), reqVO.getLat());

        for (MapPolygonDO polygonDO : polygonList) {
            String polygonId = polygonDO.getId();
            List<MapPolygonPointDO> pointDOList = polygon2PointsMap.getOrDefault(polygonId, new ArrayList<>());

            // 顶点不足3个，跳过无效多边形
            if (pointDOList.size() < 3) {
                log.warn("点在多边形判断：多边形[{}:{}]顶点数不足3个，无效", polygonId, polygonDO.getName());
                continue;
            }

            // 构建多边形结构（外环+内环）
            List<List<Point>> polygonStructure = buildPolygonStructure(pointDOList);

            // 核心逻辑：按 fenceType 判断是否符合预警条件
            boolean isInPolygon = isPointInPolygon(targetPoint, polygonStructure);
            boolean needWarn = false;
            if (fenceType == 1) {
                // 禁入围栏：点在圈内 → 需预警
                needWarn = isInPolygon;
            } else if (fenceType == 2) {
                // 禁出围栏：点在圈外 → 需预警
                needWarn = !isInPolygon;
            }

            // 符合预警条件，添加到列表
            if (needWarn) {
                log.debug("点在多边形判断：目标点{}多边形[{}:{}]，围栏类型={}，需预警",
                        isInPolygon ? "在" : "不在", polygonId, polygonDO.getName(), fenceType);
                PointInPolygonRespVO.PolygonSimpleVO simpleVO = new PointInPolygonRespVO.PolygonSimpleVO();
                simpleVO.setId(polygonId);
                simpleVO.setName(polygonDO.getName());
                simpleVO.setConfigId(polygonDO.getConfigId());
                simpleVO.setConfigName(configId2NameMap.getOrDefault(polygonDO.getConfigId(), "未知配置"));
                warnList.add(simpleVO);
            }
        }

        return warnList;
    }

    /**
     * 新增：构建多边形结构（外环+内环），抽离为单独方法提升复用性
     */
    private List<List<Point>> buildPolygonStructure(List<MapPolygonPointDO> pointDOList) {
        // 按“环索引”分组（0=外环，1+=内环）
        Map<Integer, List<Point>> ring2PointsMap = pointDOList.stream()
                .collect(Collectors.groupingBy(
                        MapPolygonPointDO::getRingIndex,
                        Collectors.mapping(p -> new Point(p.getLng(), p.getLat()), Collectors.toList())
                ));

        // 按环索引排序，确保外环在前、内环在后
        return ring2PointsMap.keySet().stream()
                .sorted()
                .map(ring2PointsMap::get)
                .collect(Collectors.toList());
    }

    /**
     * 修改：按 fenceType 生成对应的预警内容
     */
    private void createFenceWarning(PointInPolygonReqVO reqVO, MapConfigDO configDO,
                                    List<PointInPolygonRespVO.PolygonSimpleVO> warnList,
                                    LocalDateTime warningTime, Integer fenceType) {
        try {
            // 按 fenceType 生成预警类型描述
            String fenceTypeDesc;
            if (fenceType == 1) {
                fenceTypeDesc = "禁入围栏";
            } else if (fenceType == 2) {
                fenceTypeDesc = "禁出围栏";
            } else {
                fenceTypeDesc = "未知围栏";
            }

            // 构建预警内容
            StringBuilder warningContent = new StringBuilder("地址：【")
                    .append(reqVO.getAddress()).append("】触发【")
                    .append(configDO.getName())
                    .append("】")
                    .append(fenceTypeDesc);
            warnList.forEach(vo -> warningContent.append("，圈名称：").append(vo.getName()));

            // 组装预警请求
            WarningIndividualSaveReqVO saveReqVO = new WarningIndividualSaveReqVO();
            saveReqVO.setPersonId(reqVO.getPersonId());
            saveReqVO.setWarningTime(warningTime);
            saveReqVO.setWarningConditionId(configDO.getId());
            saveReqVO.setWarningContent(warningContent.toString());
            saveReqVO.setLng(reqVO.getLng());
            saveReqVO.setLat(reqVO.getLat());
            saveReqVO.setWarnType(1);
            saveReqVO.setFenceType(fenceType);

            // ---------------------- 去重逻辑开始 ----------------------
            // 1. 计算时间阈值（当前时间 - 配置的预警间隔）
            LocalDateTime timeThreshold = warningTime.minusHours(memberAppConfig.getWarnInterval());

            // 2. 查询指定时间范围内是否已有相同的预警记录
            LambdaQueryWrapper<WarningIndividualDO> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(WarningIndividualDO::getPersonId, reqVO.getPersonId())
                    .eq(WarningIndividualDO::getWarningConditionId, configDO.getId())
                    .eq(WarningIndividualDO::getFenceType, fenceType) // 假设DO中有fenceType字段
                    .ge(WarningIndividualDO::getWarningTime, timeThreshold)
                    .le(WarningIndividualDO::getWarningTime, warningTime);

            long existingCount = warningIndividualMapper.selectCount(queryWrapper);

            // 3. 如果已有记录则不重复创建
            if (existingCount > 0) {
                log.info("在预警间隔内已存在相同预警，跳过创建：人员[{}]，预警配置[{}]，围栏类型={}",
                        reqVO.getPersonId(), configDO.getId(), fenceType);
                return;
            }
            // ---------------------- 去重逻辑结束 ----------------------

            // 调用服务创建预警
            warningIndividualService.createWarningIndividual(saveReqVO);
            log.info("围栏预警创建成功：人员[{}]，预警配置[{}]，围栏类型={}，预警内容={}",
                    reqVO.getPersonId(), configDO.getId(), fenceType, warningContent);

        } catch (Exception e) {
            log.error("围栏预警创建失败：人员[{}]，预警配置[{}]，围栏类型={}，warnList={}",
                    reqVO.getPersonId(), configDO.getId(), fenceType, warnList, e);
        }
    }

    // 修正：射线法判断点是否在多边形内（支持内环，优化边界情况）
    private boolean isPointInPolygon(Point point, List<List<Point>> polygons) {
        // 空多边形直接返回false
        if (polygons.isEmpty()) return false;

        // 1. 先判断是否在外环内（外环必须是第一个元素）
        boolean inOuter = isPointInSingleRing(point, polygons.get(0), false);
        if (!inOuter) return false;

        // 2. 再判断是否在任何内环内（内环需排除）
        for (int i = 1; i < polygons.size(); i++) {
            // 内环判断逻辑与外环相反（顺时针/逆时针顺序可能不同）
            boolean inInner = isPointInSingleRing(point, polygons.get(i), true);
            if (inInner) return false; // 在任何内环内，都算在多边形外
        }

        return true;
    }

    // 修正：判断点是否在单个环内（区分内环/外环，优化边界处理）
    private boolean isPointInSingleRing(Point point, List<Point> ring, boolean isInnerRing) {
        // 环顶点不足3个，视为无效
        if (ring.size() < 3) return false;

        boolean inside = false;
        double x = point.getLng();
        double y = point.getLat();
        int n = ring.size();

        for (int i = 0; i < n; i++) {
            Point p1 = ring.get(i);
            Point p2 = ring.get((i + 1) % n); // 最后一个点与第一个点连接

            double y1 = p1.getLat();
            double y2 = p2.getLat();
            double x1 = p1.getLng();
            double x2 = p2.getLng();

            // 处理点在顶点上的情况（视为在多边形内）
            if (isSamePoint(point, p1) || isSamePoint(point, p2)) {
                return true;
            }

            // 处理点在边上的情况（视为在多边形内）
            if (isPointOnSegment(point, p1, p2)) {
                return true;
            }

            // 射线法核心：只处理与水平射线（y固定）相交的边
            // 1. 边的y范围不包含射线y，直接跳过
            if ((y1 > y) != (y2 > y)) {
                // 2. 计算射线与边的交点x坐标
                double xIntersect = ( (y - y1) * (x2 - x1) ) / (y2 - y1) + x1;

                // 3. 交点在射线右侧，且严格大于点x（避免精度问题）
                if (x < xIntersect - 1e-9) { // 减去极小值，处理精度误差
                    inside = !inside;
                }
            }
        }

        // 内环的判断结果需要反转（因顶点顺序可能为顺时针）
        return isInnerRing != inside;
    }

    // 辅助：判断两点是否相同（处理浮点数精度）
    private boolean isSamePoint(Point a, Point b) {
        return Math.abs(a.getLng() - b.getLng()) < 1e-9
                && Math.abs(a.getLat() - b.getLat()) < 1e-9;
    }

    // 修正：判断点是否在线段上（优化精度和边界）
    private boolean isPointOnSegment(Point p, Point a, Point b) {
        // 1. 先判断是否共线（叉积接近0）
        double cross = (p.getLng() - a.getLng()) * (b.getLat() - a.getLat())
                - (p.getLat() - a.getLat()) * (b.getLng() - a.getLng());
        if (Math.abs(cross) > 1e-9) {
            return false; // 不共线，直接排除
        }

        // 2. 判断点是否在a和b的x范围内
        double minX = Math.min(a.getLng(), b.getLng()) - 1e-9;
        double maxX = Math.max(a.getLng(), b.getLng()) + 1e-9;
        if (p.getLng() < minX || p.getLng() > maxX) {
            return false;
        }

        // 3. 判断点是否在a和b的y范围内
        double minY = Math.min(a.getLat(), b.getLat()) - 1e-9;
        double maxY = Math.max(a.getLat(), b.getLat()) + 1e-9;
        return p.getLat() >= minY && p.getLat() <= maxY;
    }

    // 内部点模型
    @Data
    static class Point {
        private double lng;
        private double lat;

        public Point(double lng, double lat) {
            this.lng = lng;
            this.lat = lat;
        }
    }


}
